#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         MySQLSecurityService.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  Defines a security service,
#               implementation based on the MySQL database
#               (using InnoDB tables)
#
#               NOTE: sql commands to create the appropriate
#               mysql database for this service
#               are located in the folder containing this file.
#
# See Also:     SecurityService, Permission
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Permission import *
from Products.KnowledgeEngine.SecurityService import SecurityServiceInfo, SecurityService

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile
 
#####################################################################
## Python Library Imports
# (None)

#---------------------------------------------------------------#

#MYSQL Constants
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

#####################################################################
## MySQLSecurityServiceInfo Class Definition
class MySQLSecurityServiceInfo(SecurityServiceInfo):
    """
        Defines the security service meta-information
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'MySQLSecurityService'


    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "KnowledgeEngine: MySQL Security Service"

#####################################################################
## MySQLSecurityService Class Definition
class MySQLSecurityService(SecurityService):
    """
        Defines a tool that may be used to manage Knowledge Engine
        security
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ServiceInfo implementation instance to provide info about this service
    _SERVICEINFO = MySQLSecurityServiceInfo()

    #---------------------------------------------------------------#

    def __init__(self):
        """
            Constructor for the object
        """
        
        #perform default initialisations
        SecurityService.__init__(self)
        
        #create the default properties (just the name of the mysql connection object)
        self._setProperty('dbConnectionName', '', 'string')

    #---------------------------------------------------------------#

    def getDatabaseConnection(self):
        """
            PRIVATE: Return a Zope Data Adapter (Connection object) to be
            used by this security service
        """

        #attempt to locate the Zope Database Connection 
        zda = getattr(self, self.dbConnectionName)

        #get and return a real connection from the zda
        return zda()
        
    #---------------------------------------------------------------#

    def getSpecificPermission(self, permissionIdentity, securableIdentity, actorIdentity):
        """
            PROTECTED ABSTRACT: Returns the Permission with the specified
            permissionIdentity, securableIdentity and actorIdentity.
            
            Returns None if the permission doesn't exist.
            
            Note: This method is actually to support 
            getPermission(...)
            
            RETURN TYPE: Permission
        """            
        
        #get a database connection to work with
        dbConnection = self.getDatabaseConnection()

        #determine if the assessment exists
        sql = "SELECT permissionIdentity, securableIdentity, actorIdentity, isGranted, instantEffectiveFrom, instantExpiresOn, creditStrategy, assessmentScope FROM permissions WHERE permissionIdentity = '%s' AND securableIdentity = '%s' AND actorIdentity = '%s'" % (permissionIdentity, securableIdentity, actorIdentity)
        (columns, resultset) = dbConnection.query(sql)
        
        #did we find the permission?
        if len(resultset) > 0:
            #get the permission row
            values = resultset[0]
        
            #determine the effective from time
            instantEffectiveFrom = values[4]

            #determine the expiry time
            instantExpiresOn = values[5]               
        
            #determine the assessment scope
            if values[7] is None or len(values[7].strip()) == 0:
                assessmentScope = []
            else:
                assessmentScope = eval(values[7]) 
        
            #instantiate an Permission and return it using the persistent values
            permission = Permission(values[0], values[1], values[2], values[3] == 1, instantEffectiveFrom, instantExpiresOn, values[6], assessmentScope)
            permission.setIsNew(false)
            permission.setIsModified(false)
            return permission
            
        else:
            #not found, so return nothing
            return None
        
    #---------------------------------------------------------------#

    def getPermissionsForSecurable(self, permissionIdentity, securableIdentity):
        """
            Returns the list of Permissions for all actors
            with the specified permissionIdentity and specific securableIdentity
            
            NOTE: If you require the permissions for the generic/wildcard securable
            you need to call this method explicitly with a securable of '*'
            
            NOTE: This method is primarily use to locate the permissions
            defined on securables so they can be rendered and managed through 
            a user-interface
            
            RETURN TYPE: [Permission]
        """            

        
        #get a database connection to work with
        dbConnection = self.getDatabaseConnection()

        #get the permissions for the securable
        sql = "SELECT permissionIdentity, securableIdentity, actorIdentity, isGranted, instantEffectiveFrom, instantExpiresOn, creditStrategy, assessmentScope FROM permissions WHERE permissionIdentity = '%s' AND securableIdentity = '%s'" % (permissionIdentity, securableIdentity)
        (columns, resultset) = dbConnection.query(sql)
        
        #the initial result set
        permissions = []
        
        #create permissions for each row in the result set
        for row in resultset:

            #determine the effective from time
            instantEffectiveFrom = row[4]

            #determine the expiry time
            instantExpiresOn = row[5]
        
            #determine the assessment scope
            if row[7] is None or len(row[7].strip()) == 0:
                assessmentScope = []
            else:
                assessmentScope = eval(row[7]) 
        
            #instantiate a Permission
            permission = Permission(row[0], row[1], row[2], row[3] == 1, instantEffectiveFrom, instantExpiresOn, row[6], assessmentScope)
            permission.setIsNew(false)
            permission.setIsModified(false)
            
            permissions = permissions + [permission]

        return permissions
        
    #---------------------------------------------------------------#

    def savePermission(self, permission):
        """
            Adds the specified permission to the security service.
            
            NOTE Does not store any lower-level permissions if
            the said permission is part of a permission chain.
            
            If the permission already exists, it is overwritten 
            by the provided permission.
        """
        
        #get a database connection to work with
        dbConnection = self.getDatabaseConnection()

        #determine the sql expiry time instant from the EPOCH values
        if permission.getInstantEffectiveFrom() == None:
            instantEffectiveFrom = "null"
        else:
            instantEffectiveFrom = "'%s'" % permission.getInstantEffectiveFrom().strftime(DATETIME_FORMAT)
        
        #determine the sql expires time instant from the EPOCH values
        if permission.getInstantExpiresOn() == None:
            instantExpiresOn = "null"
        else:
            instantExpiresOn = "'%s'" % permission.getInstantExpiresOn().strftime(DATETIME_FORMAT)
        
        #determine the assessmentScope
        if len(permission.getAssessmentScope()) == 0:
            assessmentScope = ""
        else:
            assessmentScope = toSQL(repr(permission.getAssessmentScope()))
        
        #create or update the permission
        if permission.getIsNew():
            #create a new permission                   
            sql = "INSERT INTO permissions (permissionIdentity, securableIdentity, actorIdentity, isGranted, instantEffectiveFrom, instantExpiresOn, creditStrategy, assessmentScope) VALUES ('%s', '%s', '%s', %d, %s, %s, '%s', '%s')" % (permission.getIdentity(), permission.getSecurableIdentity(), permission.getActorIdentity(), iif(permission.granted(),1,0), instantEffectiveFrom, instantExpiresOn, permission.getCreditStrategy(), assessmentScope)
            dbConnection.query(sql)
            
        elif permission.getIsModified():
            #update the permission (only if it is modified)
            sql = "UPDATE permissions SET isGranted = %d, instantEffectiveFrom = %s, instantExpiresOn = %s, creditStrategy = '%s', assessmentScope = '%s' WHERE permissionIdentity = '%s' AND securableIdentity = '%s' AND actorIdentity = '%s'" % (iif(permission.granted(),1,0), instantEffectiveFrom, instantExpiresOn, permission.getCreditStrategy(), assessmentScope, permission.getIdentity(), permission.getSecurableIdentity(), permission.getActorIdentity())
            dbConnection.query(sql)
        
        #the permission is no longer new or modified
        permission.setIsNew(false)
        permission.setIsModified(false)        

    #---------------------------------------------------------------#

    def removePermission(self, permission):
        """
            Removes the specified permission from the security service
        """

        #get a database connection to work with 
        dbConnection = self.getDatabaseConnection()

        #delete the permission 
        sql = "DELETE FROM permissions WHERE permissionIdentity = '%s' AND securableIdentity = '%s' AND actorIdentity = '%s'" % (permission.getIdentity(), permission.getSecurableIdentity(), permission.getActorIdentity())
        dbConnection.query(sql)
        
    #---------------------------------------------------------------#
        
#####################################################################
## Class Initialisation
# (none)

# Register Service Information with the Knowledge Engine
registerServiceClass(MySQLSecurityService)

#####################################################################
